package be.fedict.eid.diagnostics.system.windows.jna;

import java.util.ArrayList;
import java.util.List;

import be.fedict.eid.diagnostics.system.data.Device;
import be.fedict.eid.diagnostics.system.windows.proxies.SetupAPI;
import be.fedict.eid.diagnostics.system.windows.proxies.SetupAPI.HDEVINFO;
import be.fedict.eid.diagnostics.system.windows.proxies.SetupAPI.SP_DEVINFO_DATA;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.W32Errors;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

public class DeviceHandler {

	private static SetupAPI lib = SetupAPI.INSTANCE;
	private static HDEVINFO infoSetHandle;

	
	public static List<Device> getAllDevices() throws Exception {
		return getAllDevices(SetupAPI.Flags.DIGCF_ALLCLASSES | SetupAPI.Flags.DIGCF_PRESENT);
	}
	
	public static List<Device> getAllDevices(int flags) throws Exception {
		List<Device> deviceList = new ArrayList<Device>();
		infoSetHandle = lib.SetupDiGetClassDevsA(null, null, 0, flags);
		int handleValue = (new PointerByReference(infoSetHandle.getPointer())).getPointer().getInt(0);
		if (handleValue == -1) { // INVALID_HANDLE_VALUE
			throw new Exception("Could not retrieve device list: invalid handle returned.");
		} else {
			int count = 0;
			boolean more = true;
			boolean result;
			do {
				SP_DEVINFO_DATA data = new SP_DEVINFO_DATA();
				data.cbSize = data.size();
				result = lib.SetupDiEnumDeviceInfo(infoSetHandle, count++, data);				
				if (result) {
					Device device = new Device();
					device.setClassGuid(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_CLASSGUID));
					device.setDescription(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_DEVICEDESC));
					device.setDeviceClass(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_CLASS));
					device.setDriver(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_DRIVER));
					device.setEnumerator(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_ENUMERATOR_NAME));
					device.setDriver(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_DRIVER));
					device.setFriendlyName(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_FRIENDLYNAME));
					device.setHardwareId(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_HARDWAREID));
					device.setLocationInformation(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_LOCATION_INFORMATION));
					device.setManufacturer(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_MFG));
					device.setService(
							getProperty(infoSetHandle, 
									data, 
									SetupAPI.Properties.SPDRP_SERVICE));
					deviceList.add(device);
				} else {
					more = false;
				}
			} while (more);
		}
		end();
		return deviceList;
	}

	/**
	 * Retrieves a property of the USB Device.
	 */
	private static String getProperty(HDEVINFO infoSet, SP_DEVINFO_DATA devInfoData, int property) throws Exception {
		return getProperty(infoSet, devInfoData, property, 42);
	}

	private static String getProperty(
			HDEVINFO infoSet, 
			SP_DEVINFO_DATA devInfoData,
			int devProperty,
			int iBytesNeeded) throws Exception {
		
		byte[] buffer = new byte[iBytesNeeded];
		IntByReference bytesNeeded = new IntByReference(iBytesNeeded);
		boolean success = SetupAPI.INSTANCE.SetupDiGetDeviceRegistryPropertyA(
								infoSet, 
								devInfoData, 
								devProperty,
								null, 
								buffer, 
								buffer.length, 
								bytesNeeded);
		
		if (success) {
			return (new String(buffer)).trim();
		} else {
			if (Native.getLastError() == W32Errors.ERROR_INSUFFICIENT_BUFFER) {
				return getProperty(infoSet, devInfoData, devProperty, bytesNeeded.getValue()+(int)0x10);
			}
			else if (Native.getLastError() == W32Errors.ERROR_INVALID_DATA ) {
				return null;
			}
			else {
				throw new Exception("getLastError: " + Native.getLastError());
			}
		}
	}

	private static void end() throws Exception {
		if(!lib.SetupDiDestroyDeviceInfoList(infoSetHandle))
			throw new Exception("Could not destroy Device Info List.");
	}

}
